#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/decompress/mm.h>
#include <linux/of_address.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/fh_pwm.h>
#include "pwm-fullhan-common.h"
#include <mach/fh_pwm_plat.h>
#include <mach/pmu.h>
#include <linux/pm.h>

#define FH_PWM_DEBUG
#ifdef FH_PWM_DEBUG
#define PRINT_DBG(fmt, args...)  printk(fmt, ##args)
#else
#define PRINT_DBG(fmt, args...)  do {} while (0)
#endif

#define STATUS_INT				(1<<31)
#define STATUS_FINALL0			(1<<0)
#define STATUS_FINALL1			(1<<1)
#define STATUS_FINALL2			(1<<2)
#define STATUS_FINALL3			(1<<3)
#define STATUS_FINALL4			(1<<4)
#define STATUS_FINALL5			(1<<5)
#define STATUS_FINALL6			(1<<6)
#define STATUS_FINALL7			(1<<7)
#define STATUS_FINONCE0			(1<<8)
#define STATUS_FINONCE1			(1<<9)
#define STATUS_FINONCE2			(1<<10)
#define STATUS_FINONCE3			(1<<11)
#define STATUS_FINONCE4			(1<<12)
#define STATUS_FINONCE5			(1<<13)
#define STATUS_FINONCE6			(1<<14)
#define STATUS_FINONCE7			(1<<15)

#define OFFSET_PWM_BASE(n)		(PWM_REG_INTERVAL + PWM_REG_INTERVAL * n)

#define OFFSET_PWM_GLOBAL_CTRL0		(0x000)
#define OFFSET_PWM_GLOBAL_CTRL1		(0x004)
#define OFFSET_PWM_GLOBAL_CTRL2		(0x008)
#define OFFSET_PWM_INT_ENABLE		(0x010)
#define OFFSET_PWM_INT_STATUS		(0x014)

#define OFFSET_PWM_CTRL(n)			(0x000 + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_CFG0(n)			(0x004 + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_CFG1(n)			(0x008 + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_CFG2(n)			(0x00c + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_CFG3(n)			(0x010 + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_CFG4(n)			(0x014 + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_STATUS0(n)		(0x020 + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_STATUS1(n)		(0x024 + OFFSET_PWM_BASE(n))
#define OFFSET_PWM_STATUS2(n)		(0x028 + OFFSET_PWM_BASE(n))

struct pwm_chip_data {
	struct fh_pwm_chip_data chip_data;
	wait_queue_head_t wait_done;
};

struct fh_pwm_driver {
	unsigned int irq;
	struct pwm_chip chip;
	void __iomem *base;
	struct clk *clk;
	struct proc_dir_entry *proc_file;
};

struct fh_pwm_driver *fh_pwm_drv;

static void fh_pwm_output_mask(unsigned int mask)
{
	writel(mask, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL2);
}

static void fh_pwm_output_enable(unsigned int n)
{
	unsigned int reg;

	reg = readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL2);
	reg |= (1 << n);
	writel(reg, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL2);
}

static void fh_pwm_output_disable(unsigned int n)
{
	unsigned int reg;

	reg = readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL2);
	reg &= ~(1 << n);
	writel(reg, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL2);
}

static void fh_pwm_config_enable(unsigned int n)
{
	unsigned int reg;

	reg = readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL0);
	reg |= (1 << n);
	writel(reg, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL0);
}

static void fh_pwm_config_disable(unsigned int n)
{
	unsigned int reg;

	reg = readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL0);
	reg &= ~(1 << n);
	writel(reg, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL0);
}

static void __attribute__((unused)) fh_pwm_shadow_enable(unsigned int n)
{
	unsigned int reg;

	reg = readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL1);
	reg |= (1 << n);
	writel(reg, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL1);
}

static void __attribute__((unused)) fh_pwm_shadow_disable(unsigned int n)
{
	unsigned int reg;

	reg = readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL1);
	reg &= ~(1 << n);
	writel(reg, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL1);
}

static void fh_pwm_interrupt_finishall_enable(unsigned int n)
{
	void *base;

	base = (void *)(fh_pwm_drv->base + OFFSET_PWM_INT_ENABLE);
	_pwm_interrupt_finishall_enable(base, n);
}

static void fh_pwm_interrupt_finishall_disable(unsigned int n)
{
	void *base;

	base = (void *)(fh_pwm_drv->base + OFFSET_PWM_INT_ENABLE);
	_pwm_interrupt_finishall_disable(base, n);
}

static void fh_pwm_interrupt_finishonce_enable(unsigned int n)
{
	void *base;

	base = (void *)(fh_pwm_drv->base + OFFSET_PWM_INT_ENABLE);
	_pwm_interrupt_finishonce_enable(base, n);
}

static void fh_pwm_interrupt_finishonce_disable(unsigned int n)
{
	void *base;

	base = (void *)(fh_pwm_drv->base + OFFSET_PWM_INT_ENABLE);
	_pwm_interrupt_finishonce_disable(base, n);
}

static void fh_pwm_interrupt_finishonce_clear(unsigned int n)
{
	void *base;

	base = (void *)(fh_pwm_drv->base + OFFSET_PWM_INT_STATUS);
	_pwm_interrupt_finishonce_clear(base, n);
}

static void fh_pwm_interrupt_finishall_clear(unsigned int n)
{
	void *base;

	base = (void *)(fh_pwm_drv->base + OFFSET_PWM_INT_STATUS);
	_pwm_interrupt_finishall_clear(base, n);
}

static unsigned int fh_pwm_interrupt_get_status(void)
{
	unsigned int reg;

	reg = readl(fh_pwm_drv->base + OFFSET_PWM_INT_STATUS);
	reg &= readl(fh_pwm_drv->base + OFFSET_PWM_INT_ENABLE);

	return reg;
}

static void fh_pwm_get_status(struct fh_pwm_chip_data *chip_data)
{
	unsigned int status0, status1, status2;

	status0 = readl(fh_pwm_drv->base + OFFSET_PWM_STATUS0(chip_data->id));
	status1 = readl(fh_pwm_drv->base + OFFSET_PWM_STATUS1(chip_data->id));
	status2 = readl(fh_pwm_drv->base + OFFSET_PWM_STATUS2(chip_data->id));
	chip_data->status.busy = (status2 >> 4) & 0x1;
	chip_data->status.error = (status2 >> 3) & 0x1;
	chip_data->status.total_cnt = status1;
	chip_data->status.done_cnt = status0;
}

static int fh_pwm_wait_done(struct pwm_chip_data *pcd)
{
	struct fh_pwm_chip_data *chip_data = &pcd->chip_data;

	if (chip_data)
		fh_pwm_get_status(chip_data);

	if (chip_data->status.busy == 0)
		return chip_data->status.done_cnt;

	if (chip_data->config.finish_all)
		wait_event_interruptible(pcd->wait_done, chip_data->status.busy == 0);
	else {
		while (chip_data->status.busy == 1) {
			mdelay(10);
			fh_pwm_get_status(chip_data);
		}
	}
	return chip_data->status.done_cnt;
}

int fh_pwm_set_delay(unsigned int id, unsigned int delay_ns)
{
	unsigned int delay;
	unsigned int ctrl;
	unsigned int clk_rate = clk_get_rate(fh_pwm_drv->clk);

	delay = delay_ns / (NSEC_PER_SEC / clk_rate);
	fh_pwm_config_disable(id);
	writel(delay, fh_pwm_drv->base + OFFSET_PWM_CFG3(id));
	ctrl = readl(fh_pwm_drv->base + OFFSET_PWM_CTRL(id));
	if (delay_ns)
		ctrl |= 1 << 3;
	writel(ctrl, fh_pwm_drv->base + OFFSET_PWM_CTRL(id));
	fh_pwm_config_enable(id);
	return 0;
}
EXPORT_SYMBOL(fh_pwm_set_delay);

static int fh_pwm_set_config(struct fh_pwm_chip_data *chip_data)
{
	unsigned int clk_rate = clk_get_rate(fh_pwm_drv->clk);
	unsigned int ctrl = 0, period, duty, delay, phase, reg;
	unsigned int inverse = 0;

	fh_pwm_config_disable(chip_data->id);

	period = chip_data->config.period_ns / (NSEC_PER_SEC / clk_rate);
	duty = chip_data->config.duty_ns / (NSEC_PER_SEC / clk_rate);
	delay = chip_data->config.delay_ns / (NSEC_PER_SEC / clk_rate);
	phase = chip_data->config.phase_ns / (NSEC_PER_SEC / clk_rate);

	if (period > PWM_MAX_RANGE) {
		pr_err("PWM: period exceed range\n");
		return -EINVAL;
	}

	if (period == 0x0) {
		pr_err("PWM: period too low\n");
		return -EINVAL;
	}

	if (duty > PWM_MAX_RANGE) {
		pr_err("PWM: duty exceed range\n");
		return -EINVAL;
	}

	if (duty > period) {
		pr_err("PWM: duty is over period\n");
		return -EINVAL;
	}

	/* Calculate Inverse */
	phase = phase % period;
	if ((phase + duty) > period) {
		if (_pwm_need_inverse()) {
			phase   = phase + duty - period;
			duty    = period - duty;
			inverse = 1;
		}
	}

	PRINT_DBG("set period: 0x%x\n", period);
	PRINT_DBG("set duty: 0x%x\n", duty);
	PRINT_DBG("set phase: 0x%x\n", phase);
	PRINT_DBG("set delay: 0x%x\n", delay);

	writel(period, fh_pwm_drv->base + OFFSET_PWM_CFG0(chip_data->id));
	writel(duty, fh_pwm_drv->base + OFFSET_PWM_CFG1(chip_data->id));
	writel(phase, fh_pwm_drv->base + OFFSET_PWM_CFG2(chip_data->id));
	writel(delay, fh_pwm_drv->base + OFFSET_PWM_CFG3(chip_data->id));
	writel(chip_data->config.pulses,
			fh_pwm_drv->base + OFFSET_PWM_CFG4(chip_data->id));

	if (chip_data->config.delay_ns)
		ctrl |= 1 << 3;

	if (!chip_data->config.pulses)
		ctrl |= 1 << 0;

	ctrl |= (chip_data->config.stop & 0x3) << 1;

	ctrl |= inverse << 4;

	writel(ctrl, fh_pwm_drv->base + OFFSET_PWM_CTRL(chip_data->id));

	PRINT_DBG("set ctrl: 0x%x\n", ctrl);

	ctrl = readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL1);

	reg = (chip_data->config.stop >> 4) & 0x1;
	if (reg)
		ctrl |= _pwm_set_stoptime_bit(chip_data->id);
	else
		ctrl &= _pwm_set_stoptime_bit(chip_data->id);

	writel(ctrl, fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL1);

	writel(chip_data->config.pulses,
		fh_pwm_drv->base + OFFSET_PWM_CFG4(chip_data->id));
	PRINT_DBG("set pulses: 0x%x\n", chip_data->config.pulses);

	if (chip_data->config.finish_once)
		fh_pwm_interrupt_finishonce_enable(chip_data->id);
	else
		fh_pwm_interrupt_finishonce_disable(chip_data->id);

	if (chip_data->config.finish_all) {
		struct pwm_chip_data *pcd;

		pcd = pwm_get_chip_data(&fh_pwm_drv->chip.pwms[chip_data->id]);
		init_waitqueue_head(&pcd->wait_done);
		fh_pwm_interrupt_finishall_enable(chip_data->id);
	} else
		fh_pwm_interrupt_finishall_disable(chip_data->id);

	fh_pwm_config_enable(chip_data->id);

	return 0;
}

static void fh_pwm_get_config(struct fh_pwm_chip_data *chip_data)
{
	unsigned int clk_rate = clk_get_rate(fh_pwm_drv->clk);
	unsigned int ctrl = 0, period, duty, delay, phase, pulses,
			status0, status1, status2;
	struct pwm_chip_data *pcd;

	pcd = pwm_get_chip_data(&fh_pwm_drv->chip.pwms[chip_data->id]);
	period = readl(fh_pwm_drv->base + OFFSET_PWM_CFG0(chip_data->id));
	duty = readl(fh_pwm_drv->base + OFFSET_PWM_CFG1(chip_data->id));
	phase = readl(fh_pwm_drv->base + OFFSET_PWM_CFG2(chip_data->id));
	delay = readl(fh_pwm_drv->base + OFFSET_PWM_CFG3(chip_data->id));
	pulses = readl(fh_pwm_drv->base + OFFSET_PWM_CFG4(chip_data->id));
	ctrl = readl(fh_pwm_drv->base + OFFSET_PWM_CTRL(chip_data->id));
	status0 = readl(fh_pwm_drv->base + OFFSET_PWM_STATUS0(chip_data->id));
	status1 = readl(fh_pwm_drv->base + OFFSET_PWM_STATUS1(chip_data->id));
	status2 = readl(fh_pwm_drv->base + OFFSET_PWM_STATUS2(chip_data->id));


	PRINT_DBG("==============================\n");
	PRINT_DBG("pwm%d register config:\n", chip_data->id);
	PRINT_DBG("\t\tperiod: 0x%x\n", period);
	PRINT_DBG("\t\tduty: 0x%x\n", duty);
	PRINT_DBG("\t\tphase: 0x%x\n", phase);
	PRINT_DBG("\t\tdelay: 0x%x\n", delay);
	PRINT_DBG("\t\tpulses: 0x%x\n", pulses);
	PRINT_DBG("\t\tctrl: 0x%x\n", ctrl);
	PRINT_DBG("\t\tstatus0: 0x%x\n", status0);
	PRINT_DBG("\t\tstatus1: 0x%x\n", status1);
	PRINT_DBG("\t\tstatus2: 0x%x\n", status2);

	chip_data->config.period_ns = period * (NSEC_PER_SEC / clk_rate);
	chip_data->config.duty_ns = duty * (NSEC_PER_SEC / clk_rate);

	PRINT_DBG("\t\tclk_rate: %d\n", clk_rate);
	PRINT_DBG("\t\tconfig.period_ns: %d\n", chip_data->config.period_ns);
	PRINT_DBG("\t\tconfig.duty_ns: %d\n", chip_data->config.duty_ns);
	PRINT_DBG("==============================\n\n");

	chip_data->config.phase_ns = phase * (NSEC_PER_SEC / clk_rate);
	chip_data->config.delay_ns = delay * (NSEC_PER_SEC / clk_rate);
	chip_data->config.pulses = pulses;
	chip_data->config.stop = (ctrl >> 1) & 0x3;
	chip_data->config.percent = chip_data->config.duty_ns /
			(chip_data->config.period_ns / 100);
	chip_data->config.finish_once = pcd->chip_data.config.finish_once;
	chip_data->config.finish_all  = pcd->chip_data.config.finish_all;

	chip_data->status.busy = (status2 >> 4) & 0x1;
	chip_data->status.error = (status2 >> 3) & 0x1;
	chip_data->status.total_cnt = status1;
	chip_data->status.done_cnt = status0;
}

int fh_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
		int duty_ns, int period_ns)
{
	struct fh_pwm_chip_data *chip_data;

	chip_data = kzalloc(sizeof(struct fh_pwm_chip_data), GFP_KERNEL);
	if (chip_data == NULL) {
		pr_err("pwm[%d], chip data malloc failed\n", pwm->hwpwm);
		return -ENOMEM;
	}

	chip_data->id = pwm->hwpwm;
	chip_data->config.duty_ns = duty_ns;
	chip_data->config.period_ns = period_ns;

	fh_pwm_set_config(chip_data);

	kfree(chip_data);

	return 0;
}

int fh_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	fh_pwm_output_enable(pwm->hwpwm);
	return 0;
}

void fh_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	fh_pwm_output_disable(pwm->hwpwm);
}

static int fh_pwm_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int fh_pwm_release(struct inode *inode, struct file *filp)
{
	return 0;
}


static long fh_pwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	struct fh_pwm_chip_data chip_data = {0};
	struct pwm_chip_data *pcd;
	unsigned int val;

	if (unlikely(_IOC_TYPE(cmd) != PWM_IOCTL_MAGIC)) {
		pr_err("%s: ERROR: incorrect magic num %d (error: %d)\n",
			__func__, _IOC_TYPE(cmd), -ENOTTY);
		return -ENOTTY;
	}

	if (unlikely(_IOC_NR(cmd) > PWM_IOCTL_MAXNR)) {
		pr_err("%s: ERROR: incorrect cmd num %d (error: %d)\n",
			__func__, _IOC_NR(cmd), -ENOTTY);
		return -ENOTTY;
	}

	if (_IOC_DIR(cmd) & _IOC_READ)
		ret = !access_ok(VERIFY_WRITE,
			(void __user *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		ret = !access_ok(VERIFY_READ,
			(void __user *)arg, _IOC_SIZE(cmd));

	if (ret) {
		pr_err("%s: ERROR: user space access is not permitted %d (error: %d)\n",
				__func__, _IOC_NR(cmd), -EACCES);
		return -EACCES;
	}

	switch (cmd) {
	case SET_PWM_ENABLE:
		get_user(val, (unsigned int __user *)arg);
		fh_pwm_output_enable(val);
		break;
	case ENABLE_PWM:
		ret = copy_from_user((void *)&chip_data,
					(void __user *)arg,
					sizeof(struct fh_pwm_chip_data));
		if (ret != 0) {
			pr_err("ioctrl:Get chip data from user space failed\n");
			return -EFAULT;
		}

		if (chip_data.id >= fh_pwm_drv->chip.npwm) {
			pr_err("ioctrl:PWM Channel Error\n");
			return -EFAULT;
		}

		memcpy(
			fh_pwm_drv->chip.pwms[chip_data.id].chip_data,
			(void *)&chip_data,
			sizeof(struct fh_pwm_chip_data));

		fh_pwm_output_enable(chip_data.id);
		break;
	case DISABLE_PWM:
		ret = copy_from_user((void *)&chip_data,
					(void __user *)arg,
					sizeof(struct fh_pwm_chip_data));
		if (ret != 0) {
			pr_err("ioctrl:Get chip data from user space failed\n");
			return -EFAULT;
		}

		if (chip_data.id >= fh_pwm_drv->chip.npwm) {
			pr_err("ioctrl:PWM Channel Error\n");
			return -EFAULT;
		}

		memcpy(
			fh_pwm_drv->chip.pwms[chip_data.id].chip_data,
			(void *)&chip_data,
			sizeof(struct fh_pwm_chip_data));

		fh_pwm_output_disable(chip_data.id);
		break;
	case SET_PWM_DUTY_CYCLE:
		ret = copy_from_user((void *)&chip_data,
					(void __user *)arg,
					sizeof(struct fh_pwm_chip_data));
		if (ret != 0) {
			pr_err("ioctrl:Get chip data from user space failed\n");
			return -EFAULT;
		}

		if (chip_data.id >= fh_pwm_drv->chip.npwm) {
			pr_err("ioctrl:PWM Channel Error\n");
			return -EFAULT;
		}

		memcpy(
			fh_pwm_drv->chip.pwms[chip_data.id].chip_data,
			(void *)&chip_data,
			sizeof(struct fh_pwm_chip_data));
		pr_info("ioctl: SET_PWM_DUTY_CYCLE, "
			"pwm->id: %d, pwm->counter: %d, pwm->period: %d ns\n",
			chip_data.id, chip_data.config.duty_ns,
			chip_data.config.period_ns);

		ret = fh_pwm_set_config(&chip_data);
		break;
	case GET_PWM_DUTY_CYCLE:
		ret = copy_from_user((void *)&chip_data,
					(void __user *)arg,
					sizeof(struct fh_pwm_chip_data));
		if (ret != 0) {
			pr_err("ioctrl:Get chip data from user space failed\n");
			return -EFAULT;
		}
		if (chip_data.id >= fh_pwm_drv->chip.npwm) {
			pr_err("ioctrl:PWM Channel Error\n");
			return -EFAULT;
		}
		fh_pwm_get_config(&chip_data);
		ret = copy_to_user((void __user *)arg,
					(void *)&chip_data,
					sizeof(struct fh_pwm_chip_data));
		pr_info("ioctl: GET_PWM_DUTY_CYCLE, "
			"pwm->id: %d, pwm->counter: %d, pwm->period: %d ns\n",
			chip_data.id, chip_data.config.duty_ns,
			chip_data.config.period_ns);
		if (ret != 0) {
			pr_err("ioctrl:Copy chip data to user space failed\n");
			return -EFAULT;
		}
		break;
	case SET_PWM_DUTY_CYCLE_PERCENT:
		ret = copy_from_user((void *)&chip_data,
					(void __user *)arg,
					sizeof(struct fh_pwm_chip_data));
		if (ret != 0) {
			pr_err("Get chip data from user space failed\n");
			return -EFAULT;
		}

		if (chip_data.id >= fh_pwm_drv->chip.npwm) {
			pr_err("ioctrl:PWM Channel Error\n");
			return -EFAULT;
		}
		if (chip_data.config.percent > 100) {
			pr_err("ERROR: percentage is over 100\n");
			return -EIO;
		}
		chip_data.config.duty_ns = chip_data.config.period_ns *
				chip_data.config.percent / 100;
		memcpy(
			fh_pwm_drv->chip.pwms[chip_data.id].chip_data,
			(void *)&chip_data,
			sizeof(struct fh_pwm_chip_data));
		pr_info("ioctl: SET_PWM_DUTY_CYCLE_PERCENT, "
			"pwm->id: %d, pwm->counter: %d, pwm->period: %d ns\n",
			chip_data.id, chip_data.config.duty_ns,
			chip_data.config.period_ns);
		ret = fh_pwm_set_config(&chip_data);
		break;
	case ENABLE_MUL_PWM:
		get_user(val, (unsigned int __user *)arg);
		fh_pwm_output_mask(val);
		break;
	case ENABLE_FINSHALL_INTR:
		get_user(val, (unsigned int __user *)arg);
		pcd = pwm_get_chip_data(&fh_pwm_drv->chip.pwms[val]);
		init_waitqueue_head(&pcd->wait_done);
		fh_pwm_interrupt_finishall_enable(val);
		break;
	case ENABLE_FINSHONCE_INTR:
		get_user(val, (unsigned int __user *)arg);
		fh_pwm_interrupt_finishonce_enable(val);
		break;
	case DISABLE_FINSHALL_INTR:
		get_user(val, (unsigned int __user *)arg);
		fh_pwm_interrupt_finishall_disable(val);
		break;
	case DISABLE_FINSHONCE_INTR:
		get_user(val, (unsigned int __user *)arg);
		fh_pwm_interrupt_finishonce_disable(val);
		break;
	case WAIT_PWM_FINSHALL:
		get_user(val, (unsigned int __user *)arg);
		ret = fh_pwm_wait_done(fh_pwm_drv->chip.pwms[val].chip_data);
		break;
	}

	return ret;
}

static const struct file_operations fh_pwm_fops = {
	.owner				= THIS_MODULE,
	.open				= fh_pwm_open,
	.release			= fh_pwm_release,
	.unlocked_ioctl		= fh_pwm_ioctl,
};

static struct miscdevice fh_pwm_misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &fh_pwm_fops,
};

static const struct pwm_ops fh_pwm_ops = {
	.config = fh_pwm_config,
	.enable = fh_pwm_enable,
	.disable = fh_pwm_disable,
	.owner = THIS_MODULE,
};


static void del_char(char *str, char ch)
{
	char *p = str;
	char *q = str;

	while (*q) {
		if (*q != ch)
			*p++ = *q;
		q++;
	}
	*p = '\0';
}

static ssize_t fh_pwm_proc_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
	int i;
	char message[64] = {0};
	char *const delim = ",";
	char *cur = message;
	char *param_str[8];
	unsigned int param[8] = {0};
	struct fh_pwm_chip_data *chip_data;

	len = (len > 64) ? 64 : len;

	if (copy_from_user(message, buf, len))
		return -EFAULT;

	for (i = 0; i < 8; i++) {
		param_str[i] = strsep(&cur, delim);
		if (!param_str[i]) {
			pr_err("%s: ERROR: parameter[%d] is empty\n",
								__func__, i);
			pr_err("id, switch_mask, duty_ns, period_ns, "
				"numofpules, delay_ns, phase_ns, stop_status\n");
			pr_err("eg. echo '0,1,5000,10000,0,0,1000,0' > /proc/driver/pwm\n");
			return -EINVAL;
		} else {
			del_char(param_str[i], ' ');
			del_char(param_str[i], '\n');
			param[i] = (unsigned int)simple_strtoul(param_str[i], NULL, 10);
			if (param[i] < 0) {
				pr_err("%s: ERROR: parameter[%d] is incorrect\n", __func__, i);
				pr_err("id, switch_mask, duty_ns, period_ns, numofpules, "
					"delay_ns, phase_ns, stop_status\n");
				pr_err("eg. echo '0,1,5000,10000,0,0,1000,0' > /proc/driver/pwm\n");
				return -EINVAL;
			}
		}
	}

	chip_data = kzalloc(sizeof(struct fh_pwm_chip_data), GFP_KERNEL);
	if (chip_data == NULL) {
		pr_err("pwm[%d], chip data malloc failed\n", i);
		return 0;
	}

	chip_data->id = param[0];
	chip_data->config.duty_ns = param[2];
	chip_data->config.period_ns = param[3];
	chip_data->config.pulses = param[4];
	chip_data->config.delay_ns = param[5];
	chip_data->config.phase_ns = param[6];
	chip_data->config.stop = param[7];


	fh_pwm_set_config(chip_data);

	printk("set pwm %d, enable: 0x%x, duty cycle: %u ns, period cycle: %u,"
			"numofpulse: %d, delay: %d ns, phase: %d ns, stop: %d\n",
			param[0], param[1], param[2], param[3],
					param[4], param[5], param[6], param[7]);

	fh_pwm_output_mask(param[1]);

	kfree(chip_data);

	return len;
}

static void *v_seq_start(struct seq_file *s, loff_t *pos)
{
	static unsigned long counter;

	if (*pos == 0)
		return &counter;
	else {
		*pos = 0;
		return NULL;
	}
}

static void *v_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
	(*pos)++;
	return NULL;
}

static void v_seq_stop(struct seq_file *s, void *v)
{

}

static int v_seq_show(struct seq_file *sfile, void *v)
{
	int i;

	seq_printf(sfile, "\nPWM Status:\n");
	seq_printf(sfile, "global_ctrl0: 0x%x\n",
			readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL0));
	seq_printf(sfile, "global_ctrl1: 0x%x\n",
			readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL1));
	seq_printf(sfile, "global_ctrl2: 0x%x\n",
			readl(fh_pwm_drv->base + OFFSET_PWM_GLOBAL_CTRL2));

	for (i = 0; i < fh_pwm_drv->chip.npwm; i++) {
		struct pwm_chip_data *pcd;
		struct fh_pwm_chip_data *chip_data;

		pcd = pwm_get_chip_data(&fh_pwm_drv->chip.pwms[i]);
		chip_data = &pcd->chip_data;
		fh_pwm_get_config(chip_data);

		seq_printf(sfile, "id: %d \t%s, duty_ns: %u, period_ns: %u\n",
				chip_data->id,
				(chip_data->status.busy) ? "ENABLE" : "DISABLE",
				chip_data->config.duty_ns,
				chip_data->config.period_ns);
	}
	return 0;
}

static const struct seq_operations pwm_seq_ops = {
	.start = v_seq_start,
	.next = v_seq_next,
	.stop = v_seq_stop,
	.show = v_seq_show
};

static int fh_pwm_proc_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &pwm_seq_ops);
}


static const struct file_operations fh_pwm_proc_ops = {
	.owner	= THIS_MODULE,
	.open	= fh_pwm_proc_open,
	.read	= seq_read,
	.write	= fh_pwm_proc_write,
	.release = seq_release,
};

static irqreturn_t fh_pwm_interrupt(int this_irq, void *param)
{
	unsigned int status;
	unsigned int chn_once = 0xFFFFFFFF;
	unsigned int chn_all  = 0xFFFFFFFF;
	struct pwm_chip_data *pcd;

	status = fh_pwm_interrupt_get_status();
	_pwm_interrupt_process(status, &chn_once, &chn_all);

	if (chn_once != 0xFFFFFFFF)
		fh_pwm_interrupt_finishonce_clear(chn_once);

	if (chn_all != 0xFFFFFFFF) {
		struct pwm_chip chip = fh_pwm_drv->chip;
		pcd = pwm_get_chip_data(&chip.pwms[chn_all]);
		fh_pwm_get_status(&pcd->chip_data);
		wake_up_interruptible(&pcd->wait_done);
		fh_pwm_interrupt_finishall_clear(chn_all);
	}

	return IRQ_HANDLED;
}
#ifdef CONFIG_USE_OF
static struct of_device_id const fh_pwm_of_match[] = {
	{ .compatible = "fh,fh-pwm" },
	{}
};
MODULE_DEVICE_TABLE(of, fh_pwm_of_match);
#endif

static int fh_pwm_probe(struct platform_device *pdev)
{
	int err = 0;
	int i;
#ifdef CONFIG_USE_OF
	const struct of_device_id *match;
	struct device_node *np = pdev->dev.of_node;
#else
	struct resource    *res;
	struct fh_pwm_data *plat_data;
#endif

	fh_pwm_drv = kzalloc(sizeof(struct fh_pwm_driver), GFP_KERNEL);
#ifdef CONFIG_USE_OF
	match = of_match_device(fh_pwm_of_match, &pdev->dev);
	if (!match) {
		pr_info("failed to find pwm controller\n");
		return -ENODEV;
	}

	fh_pwm_drv->base = of_iomap(np, 0);
	if (fh_pwm_drv->base == NULL) {
		err = -ENXIO;
		goto done;
	}

	fh_pwm_drv->irq = irq_of_parse_and_map(np, 0);
	if (fh_pwm_drv->irq < 0) {
		dev_err(&pdev->dev, "no irq resource\n");
		goto release_region;
	}

	err = request_irq(fh_pwm_drv->irq,
		fh_pwm_interrupt, IRQF_NO_THREAD, "pwm", fh_pwm_drv);
	if (err) {
		dev_err(&pdev->dev, "failure requesting irq %i\n", fh_pwm_drv->irq);
		goto release_irqmapping;
	}

	of_property_read_u32(np, "npwm", &fh_pwm_drv->chip.npwm);
#else
	plat_data = pdev->dev.platform_data;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		err = -ENXIO;
		goto done;
	}

	res = request_mem_region(res->start, resource_size(res), pdev->name);
	if (res == NULL) {
		err = -EBUSY;
		goto done;
	}

	fh_pwm_drv->base = ioremap(res->start, resource_size(res));
	if (fh_pwm_drv->base == NULL) {
		err = -ENXIO;
		goto done;
	}

	fh_pwm_drv->irq = irq_create_mapping(NULL, platform_get_irq(pdev, 0));
	if (fh_pwm_drv->irq < 0) {
		err = -ENXIO;
		dev_err(&pdev->dev, "no irq resource?\n");
		goto release_region;
	}

	err = request_irq(fh_pwm_drv->irq,
			fh_pwm_interrupt, IRQF_NO_THREAD,
			"pwm", fh_pwm_drv);
	if (err) {
		err = -ENXIO;
		dev_err(&pdev->dev, "failure requesting irq %i\n", fh_pwm_drv->irq);
		goto release_irqmapping;
	}

	fh_pwm_drv->chip.npwm = plat_data->npwm;
#endif

	fh_pwm_drv->clk = clk_get(NULL, "pwm_clk");

	if (IS_ERR(fh_pwm_drv->clk)) {
		err = PTR_ERR(fh_pwm_drv->clk);
		goto release_irq;
	}

	clk_prepare_enable(fh_pwm_drv->clk);

	PRINT_DBG("%s: clk_rate: %lu\n", __func__, clk_get_rate(fh_pwm_drv->clk));

	err = misc_register(&fh_pwm_misc);
	if (err < 0) {
		pr_err("%s: ERROR: %s registration failed",
				__func__, DEVICE_NAME);
		goto release_irq;
	}

	fh_pwm_drv->chip.dev = &pdev->dev;
	fh_pwm_drv->chip.ops = &fh_pwm_ops;
	fh_pwm_drv->chip.base = pdev->id;

	err = pwmchip_add(&fh_pwm_drv->chip);
	if (err < 0) {
		pr_err("%s: ERROR: %s pwmchip_add failed",
			__func__, DEVICE_NAME);
		goto release_irq;
	}

	for (i = 0; i < fh_pwm_drv->chip.npwm; i++) {
		struct pwm_chip_data *pcd;

		pcd = kzalloc(sizeof(struct pwm_chip_data),
			GFP_KERNEL);
		if (pcd == NULL) {
			pr_err("pwm[%d], chip data malloc failed\n", i);
			continue;
		}
		pcd->chip_data.id = i;
		pwm_set_chip_data(&fh_pwm_drv->chip.pwms[i], pcd);
	}

	platform_set_drvdata(pdev, fh_pwm_drv);

	pr_info("PWM driver, Number: %d, IO base addr: 0x%p\n",
			fh_pwm_drv->chip.npwm, fh_pwm_drv->base);

	fh_pwm_drv->proc_file = proc_create(FH_PWM_PROC_FILE, S_IRUGO,
										NULL, &fh_pwm_proc_ops);
	if (!fh_pwm_drv->proc_file)
		pr_err("%s: ERROR: %s proc file create failed",
			__func__, DEVICE_NAME);

	dev_dbg(&pdev->dev, "PWM probe successful, IO base addr: %p\n",
		fh_pwm_drv->base);

	return 0;

release_irq:
	free_irq(fh_pwm_drv->irq, fh_pwm_drv);
release_irqmapping:
	irq_dispose_mapping(fh_pwm_drv->irq);
release_region:
	iounmap(fh_pwm_drv->base);
done:
	kfree(fh_pwm_drv);

	return err;
}

static int __exit fh_pwm_remove(struct platform_device *pdev)
{
	int err, i;
	struct fh_pwm_driver *fh_pwm_drv = platform_get_drvdata(pdev);
	proc_remove(fh_pwm_drv->proc_file);

	for (i = 0; i < fh_pwm_drv->chip.npwm; i++)
		kfree(fh_pwm_drv->chip.pwms[i].chip_data);

	err = pwmchip_remove(&fh_pwm_drv->chip);
	if (err < 0)
		return err;

	dev_dbg(&pdev->dev, "pwm driver removed\n");

	clk_disable_unprepare(fh_pwm_drv->clk);
	free_irq(fh_pwm_drv->irq, NULL);
	iounmap(fh_pwm_drv->base);
	platform_set_drvdata(pdev, NULL);
	misc_deregister(&fh_pwm_misc);

	free(fh_pwm_drv);
	fh_pwm_drv = NULL;

	return 0;
}

#ifdef CONFIG_PM
static int pwm_fh_runtime_suspend(struct device *dev)
{
	clk_disable_unprepare(fh_pwm_drv->clk);

	return 0;
}

static int pwm_fh_runtime_resume(struct device *dev)
{
	int ret;

	ret = clk_prepare_enable(fh_pwm_drv->clk);
	if (ret)
		dev_err(dev, "can't enable PWM clock, ret=%d\n", ret);

	return ret;
}

UNIVERSAL_DEV_PM_OPS(pwm_fh_pm_ops, pwm_fh_runtime_suspend,
		   pwm_fh_runtime_resume, NULL);

#define PWM_FH_PM_OPS (&pwm_fh_pm_ops)
#else
#define PWM_FH_PM_OPS NULL
#endif /* CONFIG_PM */

static struct platform_driver fh_pwm_driver = {
	.driver	= {
		.name = DEVICE_NAME,
		.owner = THIS_MODULE,
		.pm = PWM_FH_PM_OPS,
#ifdef CONFIG_USE_OF
		.of_match_table = fh_pwm_of_match,
#endif
	},
	.probe		= fh_pwm_probe,
	.remove		= __exit_p(fh_pwm_remove),
};

static int __init fh_pwm_init(void)
{
	return platform_driver_register(&fh_pwm_driver);
}

static void __exit fh_pwm_exit(void)
{

	platform_driver_unregister(&fh_pwm_driver);

}

module_init(fh_pwm_init);
module_exit(fh_pwm_exit);


MODULE_AUTHOR("fullhan");

MODULE_DESCRIPTION("Fullhan PWM device driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR);
